#import "MartinView.h"
#import "Thinker.h"
#import <math.h>
#import <libc.h>
#import <streams/streams.h>
#import <defaults/defaults.h>
#import <appkit/color.h>
#import <appkit/graphics.h>
#import <dpsclient/wraps.h>
#import <appkit/Button.h>
#import <appkit/Application.h>

/* MartinView:  a BackSpace.app v3.0 module             by Jeffrey Adams
 * version 1.0                                             jeffa@wri.com
 */

char fname[Nfunc][16] = { "martin1", "martin2", "ejk1", "ejk2" };

/* The file used to store remembered fractals */
char	fileName[] = "/.martinView";

@implementation MartinView

/* Implemented so you can screen grab the image if you want to (at least I wanted to!) */
- pause:sender
{
	isPaused = [sender state];
	return self;
}

/* Inherited method for setting the graphics state before sending OneSteps */
- didLockFocus
{
	NXSetColor(currColor);
	return self;
}

- oneStep
{
	NXRect *currentPixel = pixels;

	if (isPaused) return self;

	/* Stay in here until we hit our flush buffer limit or we hit the maximum
	   total points allowed */
	while ((++nd < nD) && (++nP < mxP)) {
	
	switch (Function) {
		case Ejk1: x1 = y  - ( (x>0) ? (B*x-C) : -(B*x-C) ); break;
		case Martin1:  x1 = y  - ( (x<0) ? sqrt(fabs(B*x-C)) : -sqrt(fabs(B*x-C)) ); break;
		case Ejk2: x1 = y  - ( (x<0) ? log(fabs(B*x-C)) : -log(fabs(B*x-C)) ); break;  
		case Martin2:  x1 = y  - sin(x); break;
		}
    y = A - x; x=x1;
	 
	/* seed perturbation */
     if (Pn && ++pn > Pn) {
	     x += (x>0) ? -Pv : Pv;
	     y += (y>0) ? -Pv : Pv;
	     pn = 0;
	     }

     /* Do we need to change the color?  */
     if (++nc > nC) {
	    color = (color+1)%Ncolors;
		/* Since we are changing color, send buffered points to screen */
		if (numPixels) {
			NXRectFillList(pixels, numPixels);
			numPixels = 0; currentPixel = pixels;
			}
		currColor = (Randomcolor) ? ((Color) ? NXConvertRGBToColor(Ranf(), Ranf(), Ranf()) : 
				NXConvertGrayToColor(Ranf())) : colors[color];
		NXSetColor(currColor);
	 	nc = 0;
	    }
	 
	/*  If we are not within the screen range, do not store them  */
    iy = cy + Zf*y; if (iy < 0 || iy > mxY) continue;
    ix = cx + Zf*x; if (ix < 0 || ix > mxX) continue;

	/* Update the data needed for the NXRectFillList */
	currentPixel->origin.x = ix;  currentPixel->origin.y = iy;
	++numPixels; ++currentPixel;

	/* Have we hit the in-range limit, if so, send the buffer to the screen */
    if (++np > mxp) {
	  	if (numPixels) {
			NXRectFillList(pixels, numPixels);
			numPixels = 0; currentPixel = pixels;
			}
	  	[self newOne:self];
		break;
		}
	}
	nd = 0;
	/* Flush buffer full, display pixels */
	if (numPixels) {
		NXRectFillList(pixels, numPixels);
		numPixels = 0; currentPixel = pixels;
		}
	/* If we left because we hit max total limit, give us a new one */
	if(nP == mxP) 
	  	[self newOne:self];
	return self;
}

- newOne:sender   
{
	double  A1, B1, C1;        /* 2nd part of parameter range             */
	char	name[15],str[20];
	NXStream	*stream;
	int	i;
		
	[self display]; 
	if (!smallInspect) return nil;

	/* Record current settings in Defaults database */
	sprintf(str,"%d", ([funcAuto state]) ? -1 : Function);
	NXWriteDefault([NXApp appName], "MartinFunction", str);
	sprintf(str,"%d", ([maxTotFlag state]) ? -1 : [maxTotalPts intValue]);
	NXWriteDefault([NXApp appName], "MartinMaxTot", str);
	sprintf(str,"%d", ([maxInFlag state]) ? -1 : [maxInRangePts intValue]);
	NXWriteDefault([NXApp appName], "MartinMaxIn", str);
	sprintf(str,"%d", ([colFlag state]) ? -1 : [colInterval intValue]);
	NXWriteDefault([NXApp appName], "MartinColorInt", str);
	
	np = 0; nP = 0; nd = 0; numPixels=0;
	x = y = 0; color = -1;
	Ranfset(time(0));
	W = (int)bounds.size.width;
	H = (int)bounds.size.height; 
	mxX = W-1; mxY = H-1;

	/* If we are using a file, load the settings from '.martinView'  */
	if (streamPos >= 0) {
		if (!(stream = NXMapFile(file, NX_READONLY))) {
			streamPos = -1;
			[useFileFlag setState:NO];
			[self newOne:self];
			return self;
			}
		for (i=0; i <= streamPos; ++i) {
			if(NXAtEOS(stream)){
				streamPos = 0; i=-1;
				NXSeek(stream, 0, NX_FROMSTART);
				if(NXAtEOS(stream)) {
					streamPos =-1;
					[useFileFlag setState:NO];
					[self newOne:self];
					return self;
					}
				continue;
				}
			NXScanf(stream, "%s\n",name);
			NXScanf(stream, "%d %le\n",&Pn,&Pv);
			NXScanf(stream, "%d\n",&Function);
			NXScanf(stream, "%le %le %le %le\n",&A,&B,&C,&Zf);
			NXScanf(stream, "%d %d %d\n",&mxp,&mxP,&nC);
			NXScanf(stream, "%d %d\n",&moveX,&moveY);
			}
		streamPos++;
		NXCloseMemory(stream, NX_FREEBUFFER);
		}
	else {
	
	if ([seedIntFlag state]) Pn = pow(10., 1+Ranf()*3);
	else Pn = [seedPertInt intValue];
	if ([seedValFlag state]) Pv = pow(10.,Ranf()*3);
	else Pv = [seedPertVal doubleValue];

   /* provide default hopalong parameters if needed */ 
   if ([funcAuto state]) {
      double r = Ranf();
      if      (r < 0.40) Function = Martin1;
      else if (r < 0.70) Function = Ejk1;
      else if (r < 0.90) Function = Ejk2;
      else               Function = Martin2;
      }
	if (Function == Martin1) {
      if ([aFlag state]) A = 40 + Ranf()*1500;
      if ([bFlag state]) B = 3  + Ranf()*17;
      if ([cFlag state]) C = 100 + Ranf()*3000;
      }
   else if (Function == Martin2) {
      if ([aFlag state]) A = 3.075927 + Ranf()*0.14;
      }
   else if (Function == Ejk1) {
      if ([aFlag state]) A = Ranf()*500;
      if ([bFlag state]) B = Ranf()*.40;
      if ([cFlag state]) C = 10 + Ranf()*100;
      }
   else if (Function == Ejk2) {
      if ([aFlag state]) A = Ranf()*500; 
      if ([bFlag state]) B = pow(10.,6+Ranf()*24);
      if ([cFlag state]) C = pow(10.,  Ranf()*9);
      }
	if(![aFlag state]) A = [hopAField doubleValue];
	if(![bFlag state]) B = [hopBField doubleValue];
	if(![cFlag state]) C = [hopCField doubleValue];
  
   if (A1 = [a1Flag state]) A = Min(A,A1) + Ranf()*(Max(A,A1) - Min(A,A1));
   if ([afFlag state] && Ranf()<0.5) A = -A;
   if (B1 = [b1Flag state]) B = Min(B,B1) + Ranf()*(Max(B,B1) - Min(B,B1));
   if ([bfFlag state] && Ranf()<0.5) B = -B;
   if (C1 = [c1Flag state]) C = Min(C,C1) + Ranf()*(Max(C,C1) - Min(C,C1));
   if ([cfFlag state] && Ranf()<0.5) C = -C;
   
   if ([magFlag state]) Zf = (Function == Martin2) ? 4.0 : 1.0;
   else Zf = [magField doubleValue];
	
	if ([maxInFlag state]) mxp = (int) (0.40* (float)(W*H));
	else mxp = [maxInRangePts intValue];
	if ([maxTotFlag state]) mxP = 4 * mxp;
	else mxP = [maxTotalPts intValue];
	
	/* color processing */
	if ([colFlag state]) nC = (mxP/Ncolors)/2;
	else nC = [colInterval intValue];
	}
	nc = nC;
	cx = W/2+moveX; cy = H/2+moveY;
	[displaceX setIntValue:moveX];
	[displaceY setIntValue:moveY];
	[seedPertInt setIntValue:Pn];
	[seedPertVal setDoubleValue:Pv];
	[dynamFlush setIntValue:nD];
	[funcButton setTitle:fname[Function]];
	[hopAField setDoubleValue:A];
   	[hopBField setDoubleValue:B];
   	[hopCField setDoubleValue:C];
   	[magField setDoubleValue:Zf];
	[maxInRangePts setIntValue:mxp];
	[maxTotalPts setIntValue:mxP];
	[colInterval setIntValue:nC];
	return self;
}
   
- changeFunc:sender
{
	char	str[10];
	
	Function = [sender selectedTag];
	[funcAuto setState:NO];
	sprintf(str,"%d", Function);
	NXWriteDefault([NXApp appName], "MartinFunction", str);
	[self newOne:sender];
	return self;
}
- changeDynam:sender
{	
	nD = [sender intValue];
	if ((nD <1) || (nD > MAXDYNAMPOINTS))
		nD = MAXDYNAMPOINTS;
	[dynamFlush setIntValue:nD];
	[self makeNewDynam:nD];
	[self newOne:sender];
	return self;
}

/* We create the array of NXRects ahead of time so we do not waste time
   building the array up as we go along */
- makeNewDynam:(int)num
{
	NXRect *curr;
	NXSize pixSize = {1,1};

	if (pixels)
	   free(pixels);
	pixels = (NXRect *)malloc(sizeof(NXRect)*(num+1));
	for (curr = pixels; curr-pixels < num; ++curr)
	    curr->size = pixSize;
	return self;
}
- setHopA:sender
{
	if ([sender doubleValue] > 0) [aFlag setState:NO];
	[self newOne:sender];
	return self;
}
- setHopB:sender
{
	if ([sender doubleValue] > 0) [bFlag setState:NO];
	[self newOne:sender];
	return self;
}
- setHopC:sender
{
	if ([sender doubleValue] > 0) [cFlag setState:NO];
	[self newOne:sender];
	return self;
}

- setSeedInt:sender
{
	if ([sender intValue] > 0) [seedIntFlag setState:NO];
	[self newOne:sender];
	return self;
}
- setMagnification:sender
{
	if ([sender doubleValue] >= 0) [magFlag setState:NO];
	[self newOne:sender];
	return self;
}

- setSeedVal:sender
{
	if ([sender doubleValue] >= 0) [seedValFlag setState:NO];
	[self newOne:sender];
	return self;
}
- setDisplacement:sender
{
	moveX = [displaceX intValue];
	moveY = [displaceY intValue];
	[self newOne:sender];
	return self;
}

- setColChange:sender
{
	char str[30];
	
	if ([sender intValue] > 0) {
		[colFlag setState:NO];
		sprintf(str,"%d", [sender intValue]);
		NXWriteDefault([NXApp appName], "MartinColorInt", str);
		}
	[self newOne:sender];
	return self;
}

- setMaxTot:sender
{
	char str[30];
	
	if ([sender intValue] > 0) {
		[maxTotFlag setState:NO];
		sprintf(str,"%d", [sender intValue]);
		NXWriteDefault([NXApp appName], "MartinMaxTot", str);
		}
	[self newOne:sender];
	return self;
}
- setMaxIn:sender
{
	char str[30];
	
	if ([sender intValue] > 0) {
		[maxInFlag setState:NO];
		sprintf(str,"%d", [sender intValue]);
		NXWriteDefault([NXApp appName], "MartinMaxIn", str);
		}
	[self newOne:sender];
	return self;
}
- setRandomColor:sender
{
	char str[10];
	
	Randomcolor = [sender state];
	sprintf(str,"%d", Randomcolor);
	NXWriteDefault([NXApp appName], "MartinRandomCol", str);
	[self newOne:sender];
	return self;
}
- changeColorMode:sender
{
	int	newColMode;
	char	str[10];
	
	newColMode = [sender selectedTag];
	if (Color != newColMode) {
		Color = newColMode;
		[self convertColors];
		sprintf(str,"%d",Color);
		NXWriteDefault([NXApp appName], "MartinColorMode", str);
		}
	[self newOne:self];
	return self;
}

/* Weak way of changing from nonrandom color array to nonrandom gray array */
- convertColors
{
	if (Color){
		Ncolors = DEFAULTNUMCOLORS;
		colors[0] = NX_COLORBLUE;  
		colors[1] = NX_COLORBROWN;
		colors[2] = NX_COLORWHITE; 
		}
	else{
		Ncolors = 3;
		colors[0] = NX_COLORWHITE;  
		colors[1] = NX_COLORDKGRAY;
		colors[2] = NX_COLORLTGRAY;
		}
	return self;
}

/* Append the current fractal info (all of it) in the data file */
- remember:sender
{
	NXStream	*stream;
	
	if (!(stream = NXMapFile(file, NX_WRITEONLY))) {
		stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
		}
	NXSeek(stream, 0, NX_FROMEND);
	NXPrintf(stream, "MartinView\n");
	NXPrintf(stream, "%d %.15le\n",Pn,Pv);
	NXPrintf(stream, "%d\n",Function);
	NXPrintf(stream, "%.15le %.15le %.15le %.15le\n",A,B,C,Zf);
	NXPrintf(stream, "%d %d %d\n",mxp,mxP,nC);
	NXPrintf(stream, "%d %d\n",moveX,moveY);
	NXSaveToFile(stream, file);
	NXCloseMemory(stream, NX_FREEBUFFER);
	return self;
}

- useFile:sender
{
	NXStream	*stream;
	char	name[15],str[10];
	
	if ([sender state]) {
		if (!(stream = NXMapFile(file, NX_READONLY))) {
			[sender setState:NO];
			streamPos = -1;
			[self newOne:self];
			}
		else {
			NXScanf(stream, "%s\n",name);
			if (strcmp("MartinView",name)) {
				[sender setState:NO];
				streamPos = -1;
				}
			else  streamPos = 0;
			NXCloseMemory(stream, NX_FREEBUFFER);
			[self newOne:self];
			}
		}
	else {
		streamPos = -1;
		[self newOne:self];
		}
	sprintf(str,"%d",[sender state]);
	NXWriteDefault([NXApp appName], "MartinUseFile", str);
	return self;
}

- initFrame:(NXRect *)frameRect
{	
	[super initFrame:frameRect];

	[self allocateGState];		// For faster lock/unlockFocus
	strcpy(file,NXHomeDirectory());
	strcat(file,fileName);
	srandom(getpid());
	isPaused = 0;
	Ncolors = DEFAULTNUMCOLORS;
	colors = (NXColor *)malloc(sizeof(NXColor)*Ncolors);
	colors[0] = NX_COLORBLUE;  
	colors[1] = NX_COLORBROWN;
	colors[2] = NX_COLORWHITE; 
	colors[3] = NX_COLORCYAN;
	colors[4] = NXConvertRGBToColor(250,128, 114);
	colors[5] = NX_COLORGRAY;
	colors[6] = NX_COLORGREEN; 
	colors[7] = NXConvertRGBToColor(255,87, 33);
	colors[8] = NX_COLORMAGENTA; 
	colors[9] = NX_COLORORANGE;
	colors[10] = NX_COLORPURPLE; 
	colors[11] = NX_COLORRED;
	colors[12] = NXConvertRGBToColor(227,207, 87); 
	colors[13] = NX_COLORYELLOW;
	moveX = 0; moveY = 0;
	Ranfseed=4326;
	nD = STARTDYNAMPOINTS;
	[self makeNewDynam:nD];	
	return self;
}

- inspectorWillBeRemoved
{
	[myPrefPanel  orderOut:self];
	return self;
}

- inspector:sender
{
    char buf[MAXPATHLEN];
	int value;

    if (!smallInspect)
	{
		sprintf(buf,"%s/MartinPrefs.nib",[sender moduleDirectory:"Martin"]);
		[NXApp loadNibFile:buf owner:self withNames:NO];
		Function=atoi(NXGetDefaultValue([NXApp appName], "MartinFunction"));
		if (Function == -1) 
			[funcAuto setState:YES];
		else {
			[funcAuto setState:NO];
			[funcButton setTitle:fname[Function]];
			}
		if ((value = atoi(NXGetDefaultValue([NXApp appName], "MartinMaxTot"))) == -1)
			[maxTotFlag setState:YES];
		else {
			[maxTotFlag setState:NO];
			[maxTotalPts setIntValue:value];
			}
		if ((value = atoi(NXGetDefaultValue([NXApp appName], "MartinMaxIn"))) == -1)
			[maxInFlag setState:YES];
		else {
			[maxInFlag setState:NO];
			[maxInRangePts setIntValue:value];
			}
		if ((value = atoi(NXGetDefaultValue([NXApp appName], "MartinColorInt"))) == -1)
			[colFlag setState:YES];
		else {
			[colFlag setState:NO];
			[colInterval setDoubleValue:value];
			}
		Randomcolor = atoi(NXGetDefaultValue([NXApp appName], "MartinRandomCol"));
		[randomFlag setState:Randomcolor];
		Color=atoi(NXGetDefaultValue([NXApp appName], "MartinColorMode"));                      
		if (Color) [colorButton setTitle:"Color"];
			else [colorButton setTitle:"Mono"];
		[self convertColors];
		currColor = (Randomcolor) ? ((Color) ? NXConvertRGBToColor(Ranf(), Ranf(), Ranf()) : 
				NXConvertGrayToColor(Ranf())) : colors[0];
		[useFileFlag setState:atoi(NXGetDefaultValue([NXApp appName], "MartinUseFile"))];
		if ([useFileFlag state]) 
			[self useFile:useFileFlag];
		else {
			streamPos =-1;
			[self newOne:self];
			}
    }
    return smallInspect;
}

+ initialize
{
	static NXDefaultsVector MartinViewDefaults = {{"MartinFunction", "-1"},
		{"MartinMaxTot", "-1"},{"MartinMaxIn", "-1"},
		{"MartinColorInt", "-1"},{"MartinRandomCol", "1"},{"MartinColorMode", "1"},
		{"MartinUseFile", "0"},{NULL,NULL} };
	NXRegisterDefaults([NXApp appName], MartinViewDefaults);
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	[self newOne:self];
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	if (!rects || !rectCount) return self;
	PSsetgray(0.0);
	NXRectFill(rects); // black screen
	return self;
}

- free
{
	if (pixels)
		free(pixels);
	if (Ncolors)
		free(colors);
   	return [super free];
}

/* Why not a little fluff */
- windowWillMiniaturize:sender toMiniwindow:miniwindow
{
 	[sender setMiniwindowIcon:"MartinMini"];
	return self;	
}

- (BOOL) useBufferedWindow {  return NO; }
- (const char *) windowTitle { return ( const char * ) "Martin Fractals";}

@end
